package th.go.drr.gps.persistance;

import java.sql.Connection;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;

import th.go.drr.gps.common.InfrastructureException;

public class HibernateUtil {

	private static Logger logger = Logger.getLogger(HibernateUtil.class);

	private static final String HIBERNATE_CFG = "hibernate.cfg.xml";
	private static Configuration configuration;
	private static SessionFactory sessionFactory;
	private static ServiceRegistry serviceRegistry;
	private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();
	private static final ThreadLocal<Transaction> threadTransaction = new ThreadLocal<Transaction>();
	private static final ThreadLocal<Interceptor> threadInterceptor = new ThreadLocal<Interceptor>();

	static {
		try {
			configuration = new Configuration();
			configuration.configure(HIBERNATE_CFG);
			serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();        
		    sessionFactory = configuration.buildSessionFactory(serviceRegistry);						
		} catch (Throwable ex) {			
			logger.error("Building SessionFactory failed.", ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {		
		return sessionFactory;
	}

	public static Configuration getConfiguration() {
		return configuration;
	}

	public static void rebuildSessionFactory()
		throws InfrastructureException {
		synchronized(sessionFactory) {
			try {
				configuration = new Configuration();
				configuration.configure(HIBERNATE_CFG);
				serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();        
			    sessionFactory = configuration.buildSessionFactory(serviceRegistry);
			} catch (Exception ex) {
				throw new InfrastructureException(ex);
			}
		}
	 }

	public static void rebuildSessionFactory(Configuration cfg)
		throws InfrastructureException {
		synchronized(sessionFactory) {
			try {
				serviceRegistry = new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry();        
			    sessionFactory = cfg.buildSessionFactory(serviceRegistry);
			} catch (Exception ex) {
				throw new InfrastructureException(ex);
			}
		}
	 }

	public static Session getSession()
		throws InfrastructureException {
		Session s = (Session) threadSession.get();
		try {
			if (s == null) {
				logger.debug("Opening new Session for this thread.");
				if (getInterceptor() != null) {					
					s = getSessionFactory().withOptions().interceptor(getInterceptor()).openSession();
				} else {
					s = getSessionFactory().openSession();
				}
				threadSession.set(s);
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
		return s;
	}
	
	public static void closeSession()
		throws InfrastructureException {
		try {
			Session s = (Session) threadSession.get();
			threadSession.set(null);
			if (s != null && s.isOpen()) {
				logger.debug("Closing Session of this thread.");
				s.close();
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	public static void beginTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			if (tx == null) {
				logger.debug("Starting new database transaction in this thread.");
				tx = getSession().beginTransaction();
				threadTransaction.set(tx);
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	public static void commitTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			if ( tx != null && !tx.wasCommitted()
							&& !tx.wasRolledBack() ) {
				logger.debug("Committing database transaction of this thread.");
				tx.commit();
			}
			threadTransaction.set(null);
		} catch (HibernateException ex) {
			rollbackTransaction();
			throw new InfrastructureException(ex);
		}
	}

	public static void rollbackTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			threadTransaction.set(null);
			if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
				logger.debug("Tyring to rollback database transaction of this thread.");
				tx.rollback();
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		} finally {
			closeSession();
		}
	}


	public static void reconnect(Session session,Connection conn)
		throws InfrastructureException {
		try {
			session.reconnect(conn);
			threadSession.set(session);
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	public static Session disconnectSession()
		throws InfrastructureException {

		Session session = getSession();
		try {
			threadSession.set(null);
			if (session.isConnected() && session.isOpen())
				session.disconnect();
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
		return session;
	}

	public static void registerInterceptor(Interceptor interceptor) {
		threadInterceptor.set(interceptor);
	}

	private static Interceptor getInterceptor() {
		Interceptor interceptor =
			(Interceptor) threadInterceptor.get();
		return interceptor;
	}

}